package org.haredot.config;

import io.jsonwebtoken.*;
import io.jsonwebtoken.impl.DefaultClaims;
import io.jsonwebtoken.impl.TextCodec;
import io.jsonwebtoken.lang.Assert;
import org.haredot.utils.MD5Utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

@Component
@EnableConfigurationProperties(JwtProperties.class)
public class JwtToken {

    private JwtProperties properties ;

    @Autowired
    private JwtToken(JwtProperties properties) {
        this.properties = properties ;
    }
    /**
     * 生成 JWT 访问 token
     * @return
     */
    public String generatorAccessToken(UserDetails userDetails) {

        String key = this.properties.getKey();

        Assert.notNull(key, "jwt.config.key is missing");

        long accessExpiredTime = this.properties.getExpiredTime();

        long expiredTime = 5 * 60 * 1000 + System.currentTimeMillis();

        // 如果 传入了 过期的时间，且 >0 , 则使用传入的过期时间
        if (accessExpiredTime > 0) {
            // 5分钟 过期
            expiredTime = System.currentTimeMillis() +
                    this.properties.getTimeUnit().toMillis(accessExpiredTime);
        }


        String username = userDetails.getUsername();

        String jti = MD5Utils.md5(username + ":"
                + TimeUnit.MILLISECONDS.toSeconds(expiredTime) + ":" + key);

        Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();

        return generatorToken(key, expiredTime, username, jti, authorities);
    }

    /**
     *
     * @param key
     * @param expiredTime : 默认过期时间， 单位毫秒
     * @param username : 用户名
     * @param jti  (AccessToken 不包含密码， RefreshToken 包含密码)
     * @return
     */
    private String generatorToken(String key, long expiredTime,
                                  String username, String jti, Collection<? extends GrantedAuthority> authorities) {

        Claims claims = new DefaultClaims()
                .setIssuer(this.properties.getIss())
                .setSubject(username)
                .setAudience(this.properties.getAud())
                .setIssuedAt(new Date())
                .setNotBefore(new Date())
                .setExpiration(new Date(expiredTime))
                .setId(jti) ;

        if (authorities !=null) {
            return Jwts.builder()
                    .setHeader(new HashMap<>(Map.of("typ", "JWT")))
                    .setClaims(claims)
                    .claim("authorities", authorities)
                    .signWith(SignatureAlgorithm.HS256, TextCodec.BASE64.encode(key))
                    .compact();
        }

        return Jwts.builder()
                .setHeader(new HashMap<>(Map.of("typ", "JWT")))
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS256, TextCodec.BASE64.encode(key))
                .compact();
    }

    /**
     * 生成刷新令牌
     * @param userDetails
     * @return
     */
    public String generatorRefreshToken(UserDetails userDetails) {

        String key = this.properties.getKey();

        Assert.notNull(key, "jwt.config.key is missing");

        long refreshExpiredTime = this.properties.getRefreshExpiredTime();

        long expiredTime = 24 * 60 * 60 * 1000 + System.currentTimeMillis();

        // 如果 传入了 过期的时间，且 >0 , 则使用传入的过期时间
        if (refreshExpiredTime > 0) {
            // 5分钟 过期
            expiredTime = System.currentTimeMillis() +
                    this.properties.getTimeUnit().toMillis(refreshExpiredTime);
        }

        String username = userDetails.getUsername();

        String jti = MD5Utils.md5(username + ":"
                + TimeUnit.MILLISECONDS.toSeconds(expiredTime) + ":" + userDetails.getPassword() + ":" + key);

        return generatorToken(key,  expiredTime, username, jti, null);

    }

    public boolean isValidToken(String accessToken) {

        boolean signed = Jwts.parser().setSigningKey(TextCodec.BASE64.encode(this.properties.getKey()))
                .isSigned(accessToken);
        if (signed) {
            try {
                Jwts.parser().setSigningKey(TextCodec.BASE64.encode(this.properties.getKey())).parseClaimsJws(accessToken);
                return true ;
            } catch (Exception e) {
                return false ;
            }
        }
        return false ;
    }

    public Claims parseJwtToClaims(String accessToken) {
        return Jwts.parser().setSigningKey(TextCodec.BASE64.encode(this.properties.getKey()))
                .parseClaimsJws(accessToken).getBody();
    }

    public boolean isValidJti(String username, Date expiration, String jti) {
        String text = username +":" + expiration.getTime()/1000 + ":" + this.properties.getKey() ;
        return Objects.equals(jti, MD5Utils.md5(text));
    }

    public boolean isValidJti(String username, Date expiration, String password, String jti) {
        String text = username +":" + expiration.getTime()/1000 +  ":" + password + ":" + this.properties.getKey() ;
        return Objects.equals(jti, MD5Utils.md5(text));
    }

    public static void main(String[] args) {
        System.out.println(new BCryptPasswordEncoder().encode("123456"));
    }
}
